#include <iostream>
#include <vector>
#include <string>
#include <boost/filesystem.hpp>
#include "util.hpp"
//src_path存放的是一个目录，其内包含原始的html网页
const std::string src_path = "data/input";
//output内存放的是将src_path内的html字符串去标签后的字符串，其内每两个html文件以\n分割，每个html文件的标题、去标签后的内容和url以\3分割
const std::string output = "data/raw_html/raw.txt";

typedef struct DocInfo
{
	std::string title;//每个文档的标题
	std::string content;//每个文档的内容
	std::string url;//每个文档的url
}DocInfo_t;

/*
  规定：
  const &:输入
  * :输出
  & :输入输出
*/
bool EnumFile(const std::string& src_path,std::vector<std::string>* file_list);
bool ParseHtml(const std::vector<std::string>& files_list,std::vector<DocInfo_t>* results);
bool SaveHtml(const std::vector<DocInfo_t>& results,const std::string& output);


int main()
{
	std::vector<std::string> files_list;
	//第一步：先递归式的将每个html文件名连带路径保存到file_list中，方便后期一个一个读取
	if(!EnumFile(src_path,&files_list))
	{
		std::cerr << "enum file name error" << std::endl; 
		return 1;
	}
	//第二步：按照files_list读取每个文件的内容并进行解析（去标签）
	std::vector<DocInfo_t> results;
	if(!ParseHtml(files_list,&results))
	{
		std::cerr << "save html error!" << std::endl;
		return 2;
	}
	//第三步：把解析后得到的各个文件内容写入到output中
	if(!SaveHtml(results,output))
	{
		std::cerr << "save html error" << std::endl;
		return 3;
	}
	return 0;
}

bool EnumFile(const std::string& src_path,std::vector<std::string>* files_list)
{
	//用path对象定义一个root_path，遍历时从src_path开始
	namespace fs = boost::filesystem; 
	fs::path root_path(src_path);

	//exists函数：判断一个路径是否存在,不存在则没有走的必要了
	if(!fs::exists(root_path))
	{
		std::cerr << src_path << "not exist" << std::endl;
		return false;
	}

	//定义一个空迭代器，当遍历结束时方便判断结束
	fs::recursive_directory_iterator end;
	for(fs::recursive_directory_iterator iter(root_path);iter != end;iter++)
	{
		//iter指向的文件必须是符合规范的（不能是图片）才能计入，html文件都是普通文件
		if(!fs::is_regular_file(*iter))
		{
			continue;
		}
		//文件的后缀必须为html，才能计入
		//path方法：用于提取当前指向的路径字符串；extension：提取后缀
		if(iter->path().extension() != ".html")
		{
			continue;
		}
		//走到此处，当前的路径一定是合法的且以.html结束的普通网页文件
		//加入到files_list时应该是一个string对象，将所有带路径的html保存到files_list中，方便后续进行文本分析
		files_list->push_back(iter->path().string());
	}
	return true;
}

static bool ParseTitle(const std::string& file,std::string* title)
{
	std::size_t begin = file.find("<title>");
	if(begin == std::string::npos)	
		return false;
	std::size_t end = file.find("</title>");
	if(end == std::string::npos)
		return false;
	begin += std::string("<title>").size();
	if(begin > end)
		return false;
	*title = file.substr(begin,end-begin);
	return true;
}
static bool ParseContent(const std::string& file,std::string* content)
{
	//去标签的动作,基于一个简易的状态机
	enum status
	{
		LABLE,   //尖括号内的内容
		CONTENT  //正常的文本内容
	};
	//任何网页最开始一定是左尖括号，初始化时先设置为LABLE
	enum status s = LABLE;
	//遍历file，只要遇到了 '>'，就意味着当前标签被处理完毕！
	//只要遇到了 '<'，就意味着新的标签开始了！！
	for(char c : file)
	{
		switch(s)
		{
			case LABLE:
				if(c == '>')
					s = CONTENT;
				break;
			case CONTENT:
				if(c == '<') //防止两个标签连着的情况
					s = LABLE;
				else
				{
					//注：不保留原始文件中的\n，因为要用\n作为后面html解析之后的文本分隔符！
					if(c == '\n')
						c = ' ';
					content -> push_back(c);
				}
				break;
			default:
				break;
		}
	}

	return true;
}
static bool ParseUrl(const std::string& file_path,std::string* url)
{
	//boost库的官方文档和我们下载下来的文档是有路径的对应关系的！
	//url_head = https://www.boost.org/doc/libs/1_80_0/doc/html (官网url)
	//url_tail = (data/input)(删除) /accumulators.html(具体的文件名) = /accumulators.html
	//url = url_head + url_tail 
	std::string url_head = "https://www.boost.org/doc/libs/1_80_0/doc/html";
	std::string url_tail = file_path.substr(src_path.size());
	*url = url_head + url_tail;
	return true;
}

void showdoc(const DocInfo_t &doc)
{
	std::cout << "title:" << doc.title << std::endl;
	std::cout << "content:" << doc.content << std::endl;
	std::cout << "url:" << doc.url << std::endl;
}
bool ParseHtml(const std::vector<std::string>& files_list,std::vector<DocInfo_t>* results)
{
	for(const std::string& file : files_list)
	{
		//1.读取文件进result
		std::string result;
		if(!ns_util::FileUtil::ReadFile(file,&result))
		{
			//读取失败一个文件，这个先不管，继续往下读
			continue;
		}
		DocInfo_t doc;
		//2.解析指定的文件，提取title
		if(!ParseTitle(result,&doc.title))
		{
			continue;
		}
		//3.解析指定的文件，提取content
		if(!ParseContent(result,&doc.content))
		{
			continue;
		}
		//4.解析指定的文件路径，构建url
		if(!ParseUrl(file,&doc.url))
		{
			continue;
		}
		//运行到此处已经完成解析任务，可以保存了
		//results -> push_back(doc); --> 若如此做，每次push_back时都要拷贝这个临时对象doc，对于内容繁多的html文件来说，可能会浪费很多时间，故采用move函数。
		//move函数作用：把要拷贝的对象在地址空间层面上与容器中的成员相关联起来，不会发生太多拷贝！
		results->push_back(std::move(doc));
		//for debug:
		//showdoc(doc);
		//break;
	}
	return true;
}
bool SaveHtml(const std::vector<DocInfo_t>& results,const std::string& output)
{	
#define SEP '\3'
	//写入文件中时一定要考虑到在往外读取时，也要方便操作！！	
	//这里采用的策略是：两个html文件以\n隔开，而每个html文件内的标题、去标签后的内容和url以\3分割。这样方便以后用getline(ifsream,line)一次直接获取一个文档内的全部内容。类似于：title\3content\3url\ntitle\3content\3url\n...
  	//以二进制写入，以便\3写入时就是\3，不会被特殊解析
	std::ofstream out(output,std::ios::out|std::ios::binary);
	if(!out.is_open())
	{
		std::cerr << "open" << output << "failed!" << std::endl;
		return false;
	}
	//此时可以进行文件写入
	for(const DocInfo_t& item : results)
	{
		std::string out_string;
		out_string = item.title;
		out_string += SEP;
		out_string += item.content;
		out_string += SEP;
		out_string += item.url;
		out_string += '\n';

		//接下来将字符串内容写入文件
		out.write(out_string.c_str(),out_string.size());
	}
	out.close();
	return true;
}













